/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: cbus_endpoint_ops.c
 *
 * Purpose: implementation endpoint ops helper functionals
 *
 * Developer:
 *   wen.gu , 2022-03-12
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "cbus/bus/cbus_endpoint_ops.h"

#include "cbus/core/cbus_mutex.h"

#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/
typedef struct _cbus_endpoint_node
{
    struct _cbus_endpoint_node* next;
    const cbus_endpoint_ops* endpoint_ops;
}cbus_endpoint_node_t;


typedef GErrc (*cbus_endpoint_ctor)(void);

static cbus_mutex_t g_endpoint_lock = NULL;
static GU32 g_is_endpoint_inited = 0;
static cbus_endpoint_node_t g_endpoint_list_head = {0};

#ifdef CBUS_ENDPOINT_CLIENT
extern GErrc cbus_client_endpoint_default_ctor(void);
#endif

#ifdef CBUS_ENDPOINT_SERVICE
extern GErrc cbus_service_endpoint_default_ctor(void);
#endif

static const cbus_endpoint_ctor g_endpoint_default_ctors[] = 
{
#ifdef CBUS_ENDPOINT_CLIENT
    cbus_client_endpoint_default_ctor,
#endif

#ifdef CBUS_ENDPOINT_SERVICE
    cbus_service_endpoint_default_ctor,
#endif
    NULL,
};
/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/
static GErrc cbus_endpoint_ops_default_ctor(void)
{
    for (GU32 i = 0; g_endpoint_default_ctors[i] != NULL; i++)
    {
        GErrc ret = g_endpoint_default_ctors[i]();

        if (G_OK != ret)
        {
            LOGE("do default ctor failed(idx: %d, ret: %d)\n", (GS32)i, ret);
            return ret;
        }
    }
    
    return G_OK;
}

static void cbus_endpoint_ops_list_free()
{
    cbus_mutex_lock(g_endpoint_lock);
    cbus_endpoint_node_t* item = g_endpoint_list_head.next;
    while (item != NULL)
    {
        cbus_endpoint_node_t* next = item->next;
        free(item);
        item = next;
    }
    g_endpoint_list_head.next = NULL;
    g_endpoint_list_head.endpoint_ops = NULL;
    cbus_mutex_unlock(g_endpoint_lock);
}

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
/**
 * @brief initislize the default endpoints
 * 
 * @return success: G_OK, else: error code @see GErrc
 */
GErrc cbus_endpoint_ops_initialize(void)
{
    if ((g_is_endpoint_inited == 0) && (g_endpoint_lock == NULL))
    {
        g_endpoint_lock = cbus_mutex_create();
        cbus_mutex_lock(g_endpoint_lock);
        g_endpoint_list_head.next = NULL;
        g_endpoint_list_head.endpoint_ops = NULL;
        g_is_endpoint_inited = 1;
        cbus_mutex_unlock(g_endpoint_lock);
        GErrc ret = cbus_endpoint_ops_default_ctor();
        if (ret != G_OK)
        {
            cbus_endpoint_ops_finish();
            return ret; 
        }
        return G_OK;
    }

    return G_ErrInvalidStatus;
}

/**
 * @brief uninitialize tansport
 * @param void none
 * @return void
 */
void cbus_endpoint_ops_finish(void)
{
    if ((g_is_endpoint_inited) && (g_endpoint_lock))
    {
        cbus_endpoint_ops_list_free();
        g_is_endpoint_inited = 0;     
        cbus_mutex_destroy(g_endpoint_lock); 
        g_endpoint_lock = NULL;       
    }
}

/**
 * @brief find a valid endpoint class with name
 * 
 * @param role [in] the role of endpoint
 * @return cbus_endpoint_ops* the reference of a endpoint class
 */
const cbus_endpoint_ops* cbus_endpoint_ops_find(cbus_endpoint_role role, const GSTR name)
{
    if ((g_is_endpoint_inited) && (g_endpoint_lock))
    {
        cbus_mutex_lock(g_endpoint_lock);
        cbus_endpoint_node_t* item = &g_endpoint_list_head;
        while (item != NULL)
        {
            const cbus_endpoint_ops* ep_ops = item->endpoint_ops;

            if (ep_ops && ep_ops->role)
            {
                if (role == ep_ops->role)
                {
                    if (name)
                    {
                        if (ep_ops->name && (strcmp(ep_ops->name, name) == 0))
                        {
                            cbus_mutex_unlock(g_endpoint_lock);
                            return ep_ops;
                        }
                    }
                    else /** if name is NULL, the reuturn the first got endpoint ops, which role is match */
                    {
                        cbus_mutex_unlock(g_endpoint_lock);
                        return ep_ops;
                    }                    
                }
            }

            item = item->next;
        }
        cbus_mutex_unlock(g_endpoint_lock);
    }

    return NULL;
}

/**
 * @brief register a endpoint class dynamicly
 * 
 * @param cbus_endpoint [in] a reference of a endpoint class
 * @return success: G_OK, else error coce @see GErrc
 */
GErrc cbus_endpoint_ops_register(const cbus_endpoint_ops* cbus_endpoint)
{
    if (!cbus_endpoint)
    {
        return G_ErrBadParameter;
    } 

    if (!g_is_endpoint_inited)
    {
        GErrc ret = cbus_endpoint_ops_initialize();

        if (G_OK != ret)
        {
            return ret;
        }
    }

    if (cbus_endpoint->version != CBUS_EP_VERSION)
    {
        return G_ErrVersionMismatch;
    }

    cbus_mutex_lock(g_endpoint_lock);
    /** check exist or goto the end of list */
    cbus_endpoint_node_t* item = & g_endpoint_list_head;
    cbus_endpoint_node_t* tail = NULL;

    while(item != NULL)
    {
        if (item->endpoint_ops)
        {
            if (item->endpoint_ops->role == cbus_endpoint->role)
            {
                if (cbus_endpoint->initialize == item->endpoint_ops->initialize)
                {
                    cbus_mutex_unlock(g_endpoint_lock);
                    return G_OK; /** if exist, then just return OK, todo refine me?? */
                }
                cbus_mutex_unlock(g_endpoint_lock);
                return G_ErrInvalidStatus;
            }
        }
        tail = item;
        item = item->next;
    }

    if (tail)
    {
        cbus_endpoint_node_t* temp = (cbus_endpoint_node_t*)malloc(sizeof(cbus_endpoint_node_t));

        if (!temp)
        {
            LOGE("alloc endpoint node failed\n");
            cbus_mutex_unlock(g_endpoint_lock);
            return G_ErrInsufficientResources;
        }

        temp->next = NULL;
        temp->endpoint_ops = cbus_endpoint;
        tail->next = temp;
        cbus_mutex_unlock(g_endpoint_lock);
        return G_OK;
    }

    cbus_mutex_unlock(g_endpoint_lock);
    return G_ErrUndefined;
}
